from iatemplate import *
from pygame.locals import * 
from vec2d import vec2d
import random
import pygame
import random
class SpinerIA(AbstractIA):
    def SpawnIAPlayer(self,controler):
         return SpinerPlayer(controler)

class WalkerIA(AbstractIA):
    def SpawnIAPlayer(self,controler):
         return WalkerPlayer(controler)
     
class SpinerPlayer(AbstractIAPlayer):
    def OnSpawn(self):        
        self.signum=90
        self.spin()
        self.controler.Fire(0)
    def OnReloaded(self):        
        self.controler.Fire(0)
    def OnAngleReached(self):
        self.spin()        
    def spin(self):
        r=random.random()<.01        
        if r:
            self.signum*=-1
        t=self.controler.GetAngle()+self.signum        
        self.controler.SetTargetAngle(t)
    def Draw(self,surface):
        view=self.controler.GetView()
        for cell in view:            
            pygame.draw.circle(surface,(200,255,255),vec2d(cell.coord)*64+(32,32),64*.2,1)
        objects=self.controler.GetObjects()
        for cell in objects:
            pygame.draw.circle(surface,(200,0,0),cell.pos*64,32,2)
        pass
        
   

class WalkerPlayer(AbstractIAPlayer):
    def OnSpawn(self):
        self.view = self.controler.GetView()        
        self.movelist=[vec2d(1,0),vec2d(0,-1),vec2d(-1,0),vec2d(0,1)]
        self.anglelist=[180,90,0,-90]
        self.OnPositionReached()
    def OnPositionReached(self):        
        self.controler.SetTargetAngle(self.controler.GetAngle()+90)
    def OnAngleReached(self):
        d=self.movelist.pop()
        self.movelist=[d]+self.movelist
        self.controler.SetTargetPosition(self.controler.GetTargetPosition()+d)    
 
        w=self.controler.GetWeapons()
        a=self.controler.GetAmmos()
        for i in range(4,-1,-1):            
            if w[i] and a[i]:                
                self.controler.Fire(i)
                break  

     
class Cell(object):
    __slots__=["lastseen","transparent","solid","walk","visible","accessible"]
    def __init__(self):
        self.lastseen=None
        self.transparent=True
        self.solid=False
        self.walk=True
        self.visible=True
        self.accessible=True        
        
            
def list2d(size):    
    return [[None for y in xrange(0,size[1])] for x in xrange(0,size[0])]
        
            
        
all_dirs=[( 1, 0),
          ( 1, 1),
          ( 0, 1),
          (-1, 1),
          (-1, 0),
          (-1,-1),
          ( 0,-1),
          ( 1,-1)]
all_dirs_p=[([( 1, 0)],100),
            ([( 1, 0),( 0, 1),( 1, 1)],140),
            ([( 0, 1)],100),
            ([(-1, 0),( 0, 1),(-1, 1)],140),
            ([(-1, 0)],100),
            ([( 0,-1),(-1, 0),(-1,-1)],140),
            ([( 0,-1)],100),
            ([( 0,-1),( 1, 0),( 1,-1)],140)]


goodlist=[   "small-medipack"
            ,"medium-medipack"
            ,"armour"
            ,"shootgun"
            ,"sootgun-ammo"
            ,"rpg"
            ,"rpg-ammo"
            ,"shrink"
            ,"shrink-ammo"
            ,"mgun"
            ,"mgun-ammo"
            ,"pistol"
            ,"pistol-ammo"]

class StrategyFailure:
    def __init__(self,s):
        self.stratrgy=s
    
class StrategyState:
    def __init__(self,player,father,childs=[]):
        self.childs=childs
        self.father=father
        self.player=player
    
    def NofitySuccess(self): 
        if self.father: self.father.OnChildSuccess(self)
        
    def OnChildSuccess(self,child): 
        pass
        
    def NofityFailure(self): 
        if self.father: self.father.OnChildFailure(self)
        
    def OnChildFailure(self,child): 
        pass
    
    def OnNotify(self,notif):
        for c in self.childs: 
            c.OnNotify(notif)
            
    def Draw(self,surface):
        for c in self.childs: 
            c.Draw(surface)
            
    def OnTick(self):
        for c in self.childs:
            c.OnTick()
        
   

class MoveToSeeCellStrategy(StrategyState):
    def __init__(self,player,father,target):
        StrategyState.__init__(self,player,father)        
        self.__move=None
        self.__see=None
        self.__location=None
        self.target=target
        
    @property
    def location(self):
        if not self.__location:                
            rpvs=self.player.ComputeRPVS(self.target)
            x,y=self.player.controler.coord
            #litle optim
            if rpvs[x][y]: return (x,y)
            potential=self.player.ComputePotential(self.player.controler.coord)
            minp=None
            minc=None
            sx,sy=self.player.mapsize
            for x in xrange(sx):
                xpot=potential[x]
                xrpvs=rpvs[x]
                for y in xrange(sy):
                    if xrpvs[y] and (minp==None or xpot[y]<minp):
                        minp=xpot[y]
                        minc=(x,y)
            if minc==None:
                raise StrategyFailure(self)
            self.__location=minc
        return self.__location
        
    
    
    @property
    def move(self):
        if not self.__move:
            #fix the strategy
            self.__move=MoveToCellStrategy(self.player,self.father,self.location)
        return self.__move
    
    @property
    def see(self):
        if not self.__see:
            #fix the strategy
            self.__see=WatchCellStrategy(self.player,self.father,self.location)
        return self.__see
    
    def OnTick(self):
        if self.see.GetTimeToComplete()>self.move.GetTimeToComplete():
            self.see.OnTick()    
        self.move.OnTick()
        
    def OnNotify(self,notif):
        if notif=="NewAreaDiscovered":
            #invalidate the strategy
            self.__see=None
            self.__move=None
            self.__location=None
        StrategyState.OnNotify(self, notif)

    def Draw(self,surface):
        m1=vec2d(self.player.controler.fcoord)*64
        m2=(self.target[0]*64+32,self.target[1]*64+32)
        pygame.draw.line(surface,(255,255,150),m1,m2,3)
        
        rpvs=self.player.ComputeRPVS(self.target)        
        for x in xrange(self.player.mapsize[0]):
            xrpve=rpvs[x]            
            for y in xrange(self.player.mapsize[1]):    
                if xrpve[y]:
                    pygame.draw.circle(surface,(225,225,150),(x*64+32,y*64+32),20,1)
        x,y=self.location
        pygame.draw.circle(surface,(225,0,0),(x*64+32,y*64+32),20,3)

        
class WatchCellStrategy(StrategyState):
    def __init__(self,player,father,target):
        StrategyState.__init__(self,player,father)
        self.target=vec2d(target)
        #cache
        self.__targetangle=None
    
    def GetTimeToComplete(self):
        rotationperquantum=2
        return abs(self.targetangle-self.player.controler.angle)/rotationperquantum
    
    @property
    def targetangle(self):
        if self.__targetangle==None:            
            angle=self.player.controler.angle
            self.__targetangle=(self.target-self.player.controler.fcoord).angle+(angle/360)*360        
            if abs(self.__targetangle-angle)>abs(self.__targetangle+360-angle):
                self.__targetangle+=360
        return self.__targetangle
    
    def OnTick(self):
        self.player.controler.SetTargetAngle(self.targetangle)

    def OnNotify(self,notif):
        if notif=="Tick":            
            self.__targetangle=None
        StrategyState.OnNotify(self, notif)
        
class MoveToCellStrategy(StrategyState):    
    def __init__(self,player,father,target):
        StrategyState.__init__(self,player,father)
        self.target=target        
                    
    def __gotonext(self):        
        controler=self.player.controler
        if controler.coord==self.target:
            self.NofitySuccess()            
            return
        
        potential=self.player.ComputePotential(self.target)
        map=self.player.map
        cx,cy=controler.coord        
        if potential[cx][cy]==None:
            #the target location is unaccessible!!
            raise StrategyFailure(self)
            
        minpot=None
        mincoord=None
        for checks,penalty in all_dirs_p:
            accessible=True
            for checkx,checky in checks:                   
                ncx=cx+checkx
                ncy=cy+checky
                try:
                    if map[ncx][ncy].walk:
                        continue #this condition is fullfiled
                except IndexError:
                    pass
                #out of map or un-available
                accessible=False
                break
                
            if accessible:    
                npot=potential[ncx][ncy]
                if npot!=None and (npot<minpot or minpot==None):
                    mincoord=(ncx,ncy)
                    minpot=npot
                    
        if mincoord==None:
            raise StrategyFailure(self)            

        if not controler.SetTargetCoord(mincoord):
            raise StrategyFailure(self)
        
        #default behavior is to watch in for of me        
        ta=(vec2d(mincoord)-controler.coord).get_angle()+int(controler.angle/360)*360
        if abs(ta-controler.angle)>abs(ta+360-controler.angle):
            ta+=360            
        controler.SetTargetAngle(ta)
    
    def GetTimeToComplete(self):
        potential=self.player.ComputePotential(self.target)        
        cx,cy=self.player.controler.coord        
        p=potential[cx][cy]
        if p==None:            
            raise StrategyFailure(self)
        return p
            
    #handle events
    def OnTick(self):        
        if not self.player.controler.iswalking:
            self.__gotonext()
            
    def Draw(self,surface):
        m1=vec2d(self.player.controler.fcoord)*64
        m2=(self.target[0]*64+32,self.target[1]*64+32)
        pygame.draw.line(surface,(200,255,255),m1,m2,3)
        potential=self.player.ComputePotential(self.target)        
        
        fnt=self.player.controler.resourcemanager.Load("freesansbold.ttf:10")
        for x in xrange(0,self.player.mapsize[0]):
            xpot=potential[x]            
            for y in xrange(0,self.player.mapsize[1]):                
                renderedtext=fnt.render(str(xpot[y]),0,(255,255,255))
                surface.blit(renderedtext, (x*64,y*64))
        

class RandomExplorerStrategy(StrategyState):      
    def __init__(self,player,father):
        StrategyState.__init__(self,player,father)
        self.childs=[MoveToCellStrategy(self.player,self,(0,0))]
        
       

class ExplorerIA(AbstractIA):
    def SpawnIAPlayer(self,controler):
         return ExplorerPlayer(controler)
     
class ExplorerPlayer(StrategyState,AbstractIAPlayer):
    #parameters
    map_learn_period=10#update the internal map representaiton
    def __init__(self,controler):
        StrategyState.__init__(self,self,None)
        #data
        self.controler=controler
        self.mapsize=self.controler.GetMapSize()        
        self.map=[[Cell() for y in xrange(0,self.mapsize[1])] for x in xrange(0,self.mapsize[0])]
        
        self.goodspawnpoints=dict()        
        self.time=0
        #pathfinding
        self.potentialcache=dict()
        self.rpvscache=dict()
        
        #anyway
        self.__LearnFormMap()
        
        #tactic        
        self.childs=[RandomExplorerStrategy(self,self)]
        
    def InvalidateMapRelatedCache(self):
        self.potentialcache.clear()
        self.rpvscache.clear()
        
    def ComputeRPVS(self,target):        
        #avoid recalculation
        tx,ty=target
        if target in self.rpvscache:
            return self.rpvscache[target]
        rpvs=self.rpvscache[target]=list2d(self.mapsize)
        for x in xrange(self.mapsize[0]):
            for y in xrange(self.mapsize[1]):
                rpvs[x][y]=self.__IsCellViewedFromCell(x,y,tx,ty)
        return rpvs
                
    def ComputePotential(self,target):        
        #avoid recalculation
        if target in self.potentialcache:
            return self.potentialcache[target]        
        #Initialize the feild of potentials
        potential=self.potentialcache[target]=list2d(self.mapsize)
        map=self.map        
        tx,ty=target
        sx,sy=self.mapsize
        #Check the target
        tcell=map[tx][ty]
        if not tcell.walk: 
            return potential
        
        #Initialize the algorythm
        potential[tx][ty]=0
        queue=list()
        for dx,dy in all_dirs:
            cx=tx+dx
            cy=ty+dy
            if cx>=0 and cy>=0 and cx<sx and cy<sy:
                cell=map[cx][cy]
                if cell.walk:
                    queue.append((cx,cy))
                        
        while queue:            
            tqueue=list()
            cx,cy=queue.pop()            
            newpot=None
            for checks,penalty in all_dirs_p:
                accessible=True
                for checkx,checky in checks:                   
                    ncx=cx+checkx
                    ncy=cy+checky
                    if ncx<0 or ncy<0 or ncx>=sx or ncy>=sy or not map[ncx][ncy].walk:
                        accessible=False
                        break
                if accessible:                    
                # the last computed ncx, ncy are the destination's coordinates and it is reachable
                    p=potential[ncx][ncy]
                    if p!=None:
                        p+=penalty
                        if newpot==None or p<newpot:
                            newpot=p
                    tqueue.append((ncx,ncy))
                        
            if newpot!=None:                
                curpot=potential[cx][cy]
                if curpot==None or newpot<curpot:
                    potential[cx][cy]=newpot
                    queue.extend(tqueue)
        
        self.__LearnFormPotential(potential)
        return potential

    
    def __LearnFormPotential(self,potential):
        x,y=self.controler.coord 
        if potential[x][y]==None:
            #well the potential not acccessible
            for x in xrange(0,self.mapsize[0]):
                xpots= potential[x]
                xmaps= self.map[x]
                for y in xrange(0,self.mapsize[1]):
                    if xpots[y]!=None:
                        xmaps[y].accessible=False
        
    def __LearnFormMapIfRequired(self):
        if abs(self.time-self.learnfrommap_time)>100 or abs(self.learnfrommap_angle-self.controler.angle)>10:
            self.__LearnFormMap()
            
    def __LearnFormMap(self):
        self.learnfrommap_angle=self.controler.angle        
        self.learnfrommap_time=self.time
        newarea=False
        for cellc in self.controler.GetView():
            x,y=cellc.coord            
            c=self.map[x][y]            
            if c.lastseen==None:
                newarea=True
                c.transparent=cellc.transparent
                c.solid=cellc.solid
                c.walk=cellc.walk
                if not c.walk:
                    c.accessible=False                                            
            c.lastseen=self.time
    
        for objc in self.controler.GetObjects():
            type=objc.type  
            if type in goodlist:
                type=objc.type                
                if not type in self.goodspawnpoints:
                    self.goodspawnpoints[type]=set()
                self.goodspawnpoints[type].add(objc.coord)                

        if newarea:            
            self.InvalidateMapRelatedCache()
            self.OnNotify("NewAreaDiscovered")
        pass
    
    def __IsCellViewedFromCell(self,eyex,eyey,coordx,coordy): 
        if not self.map[eyex][eyey].transparent:
            return False
        
        #It is the same location, it is allays visible avoid 1/0
        if eyex==coordx and eyey==coordy:
            return True        
                
        p0x=eyex+.5 ; p0y=eyey+.5        
        #assume that the cell is transparent
        p1x=coordx+(.05,.95)[eyex>coordx]
        p1y=coordy+.5            
        if self.__IsPosViewedFromPos((p0x,p0y),(p1x,p1y)):                
            return True        
        
        p1x=coordx+.5            
        p1y=coordy+(.05,.95)[eyey>coordy]            
        if self.__IsPosViewedFromPos((p0x,p0y),(p1x,p1y)):                
            return True
        
        return False
    
    def __IsPosViewedFromPos(self,eye_fc,pos_fc):        
        p0x,p0y = eye_fc
        p1x,p1y = pos_fc
        dx = p1x-p0x     ;dy = p1y-p0y
        coord0x=int(p0x) ;coord0y=int(p0y)
        coord1x=int(p1x) ;coord1y=int(p1y)
        
        #No more intersection
        while coord0x!=coord1x or coord0y!=coord1y:
            if not self.map[coord0x][coord0y].transparent:
                return False
            cornerx=float(coord0x+(dx>0))
            cornery=float(coord0y+(dy>0))
            det=dx*(cornery-p0y) - dy*(cornerx-p0x)
            
            vertical=((dx>0)==(dy>0))==(det>0)
            if vertical:
                #intersection with a vertial line
                p0y+=dy*(cornerx-p0x)/dx 
                p0x=float(cornerx) # x is projected on the line
                if dx>0: coord0x+=1
                else:    coord0x-=1                  
            else:
                #intersection with a horizontal line
                p0x+=dx*(cornery-p0y)/dy
                p0y=float(cornery) # y is projected on the line
                if dy>0: coord0y+=1
                else:    coord0y-=1
        return True

    def OnTick(self):
        self.time+=1
        self.__LearnFormMapIfRequired()
        StrategyState.OnTick(self)
        nw=max(n for n,q in enumerate(self.controler.weapons) if q)
        self.controler.Fire(nw)
        
        
    def Draw(self,surface):
        StrategyState.Draw(self,surface)
        view=self.controler.GetView()
        for x in range(0,self.mapsize[0]):
            for y in range(0,self.mapsize[1]):
                c=self.map[x][y]
                if not c.accessible:
                    pygame.draw.circle(surface,(200,0,0),(x*64+32,y*64+32),20,1)
                elif c.lastseen==None:
                    pygame.draw.circle(surface,(200,200,0),(x*64+32,y*64+32),20,1)
                elif not c.walk:
                    pygame.draw.circle(surface,(0,0,100),(x*64+32,y*64+32),20,1)
                    
    def Draw2(self,surface):
        view=self.controler.GetView()
        for cell in view:            
            pygame.draw.circle(surface,(200,255,255),vec2d(cell.coord)*64+(32,32),64*.2,1)
        objects=self.controler.GetObjects()        
        pass
    
   
  

exported=[ExplorerIA]